module Rack # :nodoc:
  
  # Rack::Html5 sets custom headers for each HTML5 feature the browser supports.
  # It does this by parsing the user agent and matching the features to the browser version based on information provided on:  http://caniuse.com/#eras=farpast,past,now&cats=HTML5&statuses=rec,pr,cr,wd,ietf&nodetails=1
  #
  # The headers that are being set: 
  # * HTTP_X_DETECTED_BROWSER: Browser that has been DETECTED, eg.: Firefox
  # * HTTP_X_DETECTED_VERSION: Version of the browser that has been DETECTED, eg.: 3.6.6
  # * HTTP_X_SUPPORTS_HTML5_WYSIWYG: WYSIWYG editable elements
  # * HTTP_X_SUPPORTS_HTML5_CLASSNAME: getElementsByClassName
  # * HTTP_X_SUPPORTS_HTML5_ELEMENTS: Stylable HTML5 elements
  # * HTTP_X_SUPPORTS_HTML5_CANVAS: Canvas (basic support)
  # * HTTP_X_SUPPORTS_HTML5_MESSAGING: Cross-document messaging
  # * HTTP_X_SUPPORTS_HTML5_AUDIO: Audio element
  # * HTTP_X_SUPPORTS_HTML5_VIDEO: Video element
  # * HTTP_X_SUPPORTS_HTML5_TEXTAPI: Text API for canvas
  # * HTTP_X_SUPPORTS_HTML5_DRAGANDDROP: Drag and drop
  # * HTTP_X_SUPPORTS_HTML5_OFFLINE: Offline web applications
  # * HTTP_X_SUPPORTS_HTML5_SVG: Inline SVG
  # * HTTP_X_SUPPORTS_HTML5_FORMS: Form features (Web Forms 2.0)
  #
  # Please note:
  # * If the feature is not supported, the header will not be set.
  # * If a browser implements a feature only partially, the corresponding header will not be set.
  class Html5
    attr_accessor :version, :browser, :user_agent, :features, :env
    
    def initialize(app)
      @app = app
      @features = []      
    end
    
    def generate_headers
      @env['HTTP_X_DETECTED_VERSION'] = version
      @env['HTTP_X_DETECTED_BROWSER'] = browser.to_s
      @features.each do |feature|
        @env["HTTP_X_SUPPORTS_HTML5_#{feature.to_s.upcase}"] = "true"
      end
    end

    def call(env)
      @env = env      
      @user_agent = @env['HTTP_USER_AGENT']
      
      detect_browser
      detect_features
      generate_headers
      
      @app.call(@env)
    end

    # Detects the browser and version based on the user agent
    def detect_browser     
      user_agent_matcher.each do |browser, regexp|        
        match = @user_agent.match(regexp)
        version = match.captures.first if match
        if version
          @browser = browser
          @version = version
          break
        end
      end
    end
    
    # Detects the HTML5 features the browser supports
    def detect_features
      return unless @browser && @version
      feature_mapping[browser].each do |feature, version|
         if version           
           raise "Versions are not comparable: #{version}, #{@version}" if comparable_version(version).length != comparable_version(@version).length
           if comparable_version(@version) >= comparable_version(version)
             @features << feature
           end
         end
       end
    end
    
    # New features are only being shipped with major version updated.
    # So we'll be on the safe side using the first two parts of the version number for comparisation.
    # eg. version 10.5.6a will be cut down to 10.5, dots will be stripped, return value would be 105
    def comparable_version(v)
      v.split(".")[0..1].join
    end

    # Feature to browser version mapping according to the information provided on: http://caniuse.com/#eras=farpast,past,now&cats=HTML5&statuses=rec,pr,cr,wd,ietf&nodetails=1
    def feature_mapping
      {
        :firefox => {          
          :wysiwyg => "3.5",
          :classname => "3.0",
          :elements => "3.0",
          :canvas => "3.0",
          :messaging => "3.0",
          :audio => "3.5",
          :video => "3.5", 
          :textapi => "3.5",
          :draganddrop => "3.5", 
          :offline => "3.5", 
          :forms => nil,
          :svg => nil
        },
        :ie => {          
          :wysiwyg => "6.0",
          :classname => nil,
          :elements => nil,
          :canvas => nil,
          :messaging => "8.0",
          :audio => nil,
          :video => nil,
          :textapi => nil,
          :draganddrop => nil,
          :offline => nil,
          :forms => nil,
          :svg => nil
        }, 
        :opera => {
          :wysiwyg => "10.1",
          :classname => "10.1",
          :elements => "10.1",
          :canvas => "10.1",
          :messaging => "10.1",
          :audio => "10.5",
          :video => "10.5",
          :textapi => "10.5",
          :draganddrop => nil,
          :offline => "10.6",
          :forms => "10.1",
          :svg => nil          
        },         
        :chrome => {
          :wysiwyg => "3.0",
          :classname => "3.0",
          :elements => "3.0",
          :canvas => "3.0",
          :messaging => "3.0",
          :audio => "3.0",
          :video => "3.0",
          :textapi => "3.0",
          :draganddrop => "3.0",
          :offline => "4.0",
          :forms => nil,
          :svg => nil          
        }, 
        :safari => {
          :wysiwyg => "3.2",
          :classname => "3.2",
          :elements => "3.2",
          :canvas => "3.2",
          :messaging => "4.0",
          :audio => "3.2",
          :video => "3.2",
          :textapi => "4.0",
          :draganddrop => "4.0",
          :offline => "4.0",
          :forms => nil,
          :svg => nil          
        }
      }
    end

    # Browser to user agent matching
    def user_agent_matcher
      regexp_version = /((\d+\.?)+)/
      { 
        :firefox => /Firefox\/#{regexp_version}/, # eg. Firefox/1.5.0.12
        :ie => /MSIE #{regexp_version}/, # eg. compatible; MSIE 7.0;
        :chrome => /Chrome\/#{regexp_version}/, # eg. Chrome/5.0.307.11 Safari/532.9
        :safari => /Mac .*AppleWebKit\/.*Version\/#{regexp_version}/, # eg. AppleWebKit/531.21.8 Version/4.0.4 Safari/531.21.10              
        :opera => /Opera\/#{regexp_version}/, # eg. Opera/9.80
      }
    end  
  
  end
end